package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 虎哥
 */
@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    /**
     * 把Spu转换为Goods
     *
     * @param spu 商品信息
     * @return Goods
     */
    public Goods buildGoods(SpuDTO spu) {
        // 1. 搜索关键字，包含分类、品牌、名称等。。
        // 1.1.查询分类
        String categoryName = itemClient.queryCategoryByIds(spu.getCategoryIds())
                .stream().map(CategoryDTO::getName).collect(Collectors.joining(" "));
        // 1.2.查询品牌
        String brandName = itemClient.queryBrandById(spu.getBrandId()).getName();
        // 1.3.拼接内容
        String all = spu.getName() + categoryName + brandName;

        // 2.spu下的sku的集合
        List<SkuDTO> skuList = itemClient.querySkuBySpuId(spu.getId());
        List<Map<String, Object>> skuMap = new ArrayList<>();
        for (SkuDTO skuDTO : skuList) {
            Map<String, Object> map = new HashMap<>();
            map.put("id", skuDTO.getId());
            map.put("title", skuDTO.getTitle());
            map.put("price", skuDTO.getPrice());
            map.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            skuMap.add(map);
        }
        // 3.sku的价格的集合
        Set<Long> prices = skuList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        // 4.商品规格参数，key:规格参数名称，value：规格参数的值
        Map<String, Object> specs = new HashMap<>();
        // 4.1.规格参数的key, 当前商品分类下，需要搜索过滤的规格参数
        List<SpecParamDTO> specParams = itemClient.querySpecParams(null, spu.getCid3(), true);

        // 4.2.规格参数的值，查询spuDetail
        SpuDetailDTO spuDetail = itemClient.querySpuDetailById(spu.getId());
        // 4.2.1.取出通用规格参数的值
        Map<Long, Object> genericSpec = JsonUtils.toMap(spuDetail.getGenericSpec(), Long.class, Object.class);
        // 4.2.2.取出特有规格参数
        Map<Long, List<String>> specialSpec = JsonUtils.nativeRead(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });

        // 4.3.填写map
        for (SpecParamDTO param : specParams) {
            // 取出规格参数名称，作为specs的key
            String key = param.getName();
            // 获取规格参数的值
            Object value = null;
            // 判断是否是通用规格参数
            if (param.getGeneric()) {
                // 通用规格，去GenericSpec找
                value = genericSpec.get(param.getId());
            } else {
                // 特有规格，去specialSpec找
                value = specialSpec.get(param.getId());
            }

            // 判断规格是否是数值类型，如果是，则转成段来存储
            if (param.getNumeric()) {
                // 分段处理
                value = chooseSegment(value, param);
            }
            // 写入specs
            specs.put(key, value);
        }

        // 创建Goods
        Goods goods = new Goods();
        goods.setSubTitle(spu.getSubTitle());
        goods.setId(spu.getId());
        goods.setCategoryId(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setCreateTime(spu.getCreateTime().getTime());
        // 搜索关键字，包含分类、品牌、名称等。。
        goods.setAll(all);
        // sku的价格的集合
        goods.setPrice(prices);
        // spu下的sku的集合
        goods.setSkus(JsonUtils.toString(skuMap));
        // 商品规格参数，key:规格参数名称，value：规格参数的值
        goods.setSpecs(specs);
        return goods;
    }

    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    @Autowired
    private ElasticsearchTemplate esTemplate;

    /**
     * 搜索数据
     *
     * @param request
     * @return
     */
    public PageResult<GoodsDTO> search(SearchRequest request) {
        // 0.健壮性判断
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        // 1.构建查询条件
        // 1.1.创建查询条件构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 1.2.source的过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "skus", "subTitle"}, null));
        // 1.3.关键字搜索
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        // 1.4.分页
        int page = request.getPage() - 1;
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));

        // 2.搜索结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        // 3.解析结果
         long total = result.getTotalElements();
        int totalPages = result.getTotalPages();
        List<Goods> content = result.getContent();
        // 3.2.转换DTO
        List<GoodsDTO> list = BeanHelper.copyWithCollection(content, GoodsDTO.class);

        return new PageResult<>(total, totalPages, list);
    }

    private QueryBuilder buildBasicQuery(SearchRequest request) {
        // 1.构建布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 2.添加关键字搜索
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        // 3.添加其它过滤项
        Map<String, String> filter = request.getFilter();
        for (Map.Entry<String, String> entry : filter.entrySet()) {
            // 获取entry的key，就是搜索的字段
            String key = entry.getKey();
            // 处理key
            if ("分类".equals(key)) {
                key = "categoryId";
            } else if ("品牌".equals(key)) {
                key = "brandId";
            } else {
                key = "specs." + key;
            }
            // 构建查询
            queryBuilder.filter(QueryBuilders.termQuery(key, entry.getValue()));
        }
        return queryBuilder;
    }

    /**
     * 在搜索条件的基础上聚合，得到过滤项
     *
     * @param request
     * @return
     */
    public Map<String, List<?>> getFilter(SearchRequest request) {
        // 0.健壮性判断
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        // 1.构建查询条件
        // 1.1.创建查询条件构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 1.2.source的过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[0], null));
        // 1.3.关键字搜索
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        // 1.4.分页
        queryBuilder.withPageable(PageRequest.of(0, 1));

        // 1.5聚合
        // 1.5.1.分类的聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("categoryAgg").field("categoryId"));
        // 1.5.2.品牌的聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brandId"));

        // 2.发起请求，获取结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 3.获取聚合结果
        Aggregations aggregations = result.getAggregations();

        Map<String, List<?>> filterList = new LinkedHashMap<>();
        // 3.1.获取分类的聚合
        Terms categoryTerm = aggregations.get("categoryAgg");
        List<Long> idList = handleCategoryAgg(categoryTerm, filterList);

        // 3.2.获取品牌的聚合
        Terms brandTerm = aggregations.get("brandAgg");
        handleBrandAgg(brandTerm, filterList);

        // 4.处理规格参数聚合
        if (idList != null && idList.size() == 1) {
            // 说明分类只剩下一个，可以处理了，需要3个参数：商品分类id，查询的条件，过滤项的map
            handleSpecAgg(idList.get(0), basicQuery, filterList);
        }
        return filterList;
    }

    private void handleSpecAgg(Long cid, QueryBuilder basicQuery, Map<String, List<?>> filterList) {

        // 1.查询这个分类下的需要搜索的规格参数
        List<SpecParamDTO> params = itemClient.querySpecParams(null, cid, true);

        // 2.构建查询条件
        // 2.1.创建查询条件构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.2.source的过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[0], null));
        // 2.3.关键字搜索
        queryBuilder.withQuery(basicQuery);
        // 2.4.分页
        queryBuilder.withPageable(PageRequest.of(0, 1));

        // 3.添加聚合条件
        for (SpecParamDTO param : params) {
            // 获取参数的名称，作为聚合名称
            String name = param.getName();
            // 添加聚合
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }

        // 4.发起请求，获取结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 5.解析结果
        Aggregations aggregations = result.getAggregations();

        for (SpecParamDTO param : params) {
            // 获取参数名称，作为聚合名称
            String name = param.getName();
            // 根据聚合名称，获取聚合结果
            Terms terms = aggregations.get(name);
            // 获取terms中的桶中的key
            List<String> list = terms.getBuckets().stream()
                    .map(Terms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toList());
            // 封装结果
            filterList.put(name, list);
        }
    }

    private void handleBrandAgg(Terms terms, Map<String, List<?>> filterList) {
        // 从聚合中，获取id的集合
        List<Long> idList = terms.getBuckets().stream()
                .map(b -> (b.getKeyAsNumber().longValue())).collect(Collectors.toList());
        // 根据id的集合，查询分类的集合
        List<BrandDTO> list = itemClient.queryBrandByIds(idList);
        // 封装结果
        filterList.put("品牌", list);
    }

    private List<Long> handleCategoryAgg(Terms terms, Map<String, List<?>> filterList) {
        // 从聚合中，获取id的集合
        List<Long> idList = terms.getBuckets().stream()
                .map(b -> (b.getKeyAsNumber().longValue())).collect(Collectors.toList());
        // 根据id的集合，查询分类的集合
        List<CategoryDTO> list = itemClient.queryCategoryByIds(idList);
        // 封装结果
        filterList.put("分类", list);
        return idList;
    }

    @Autowired
    private GoodsRepository repository;

    public void saveGoods(Long spuId) {
        // 查询spu
        SpuDTO spu = itemClient.querySpuById(spuId);
        // 构建goods
        Goods goods = buildGoods(spu);
        // 新增Goods
        repository.save(goods);
    }

    public void deleteGoodsById(Long spuId) {
        repository.deleteById(spuId);
    }
}
